package org.andengine.util.modifier;

import org.andengine.util.modifier.IModifier.IModifierListener;

import java.util.Arrays;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @param <T>
 * @author Nicolas Gramlich
 * @since 11:21:22 - 03.09.2010
 */
public class ParallelModifier<T> extends BaseModifier<T> implements IModifierListener<T> {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private float mSecondsElapsed;
    private final float mDuration;

    private final IModifier<T>[] mModifiers;
    private boolean mFinishedCached;

    // ===========================================================
    // Constructors
    // ===========================================================

    public ParallelModifier(final IModifier<T>... pModifiers) throws IllegalArgumentException {
        this(null, pModifiers);
    }

    public ParallelModifier(final IModifierListener<T> pModifierListener, final IModifier<T>... pModifiers) throws IllegalArgumentException {
        super(pModifierListener);

        if (pModifiers.length == 0) {
            throw new IllegalArgumentException("pModifiers must not be empty!");
        }

        BaseModifier.assertNoNullModifier(pModifiers);

        Arrays.sort(pModifiers, MODIFIER_COMPARATOR_DURATION_DESCENDING);
        this.mModifiers = pModifiers;

        final IModifier<T> modifierWithLongestDuration = pModifiers[0];
        this.mDuration = modifierWithLongestDuration.getDuration();
        modifierWithLongestDuration.addModifierListener(this);
    }

    @SuppressWarnings("unchecked")
    protected ParallelModifier(final ParallelModifier<T> pParallelModifier) throws DeepCopyNotSupportedException {
        final IModifier<T>[] otherModifiers = pParallelModifier.mModifiers;
        this.mModifiers = new IModifier[otherModifiers.length];

        final IModifier<T>[] modifiers = this.mModifiers;
        for (int i = modifiers.length - 1; i >= 0; i--) {
            modifiers[i] = otherModifiers[i].deepCopy();
        }

        final IModifier<T> modifierWithLongestDuration = modifiers[0];
        this.mDuration = modifierWithLongestDuration.getDuration();
        modifierWithLongestDuration.addModifierListener(this);
    }

    @Override
    public ParallelModifier<T> deepCopy() throws DeepCopyNotSupportedException {
        return new ParallelModifier<T>(this);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public float getSecondsElapsed() {
        return this.mSecondsElapsed;
    }

    @Override
    public float getDuration() {
        return this.mDuration;
    }

    @Override
    public float onUpdate(final float pSecondsElapsed, final T pItem) {
        if (this.mFinished) {
            return 0;
        } else {
            float secondsElapsedRemaining = pSecondsElapsed;

            final IModifier<T>[] shapeModifiers = this.mModifiers;

            this.mFinishedCached = false;
            while (secondsElapsedRemaining > 0 && !this.mFinishedCached) {
                float secondsElapsedUsed = 0;
                for (int i = shapeModifiers.length - 1; i >= 0; i--) {
                    secondsElapsedUsed = Math.max(secondsElapsedUsed, shapeModifiers[i].onUpdate(pSecondsElapsed, pItem));
                }
                secondsElapsedRemaining -= secondsElapsedUsed;
            }
            this.mFinishedCached = false;

            final float secondsElapsedUsed = pSecondsElapsed - secondsElapsedRemaining;
            this.mSecondsElapsed += secondsElapsedUsed;
            return secondsElapsedUsed;
        }
    }

    @Override
    public void reset() {
        this.mFinished = false;
        this.mSecondsElapsed = 0;

        final IModifier<T>[] shapeModifiers = this.mModifiers;
        for (int i = shapeModifiers.length - 1; i >= 0; i--) {
            shapeModifiers[i].reset();
        }
    }

    @Override
    public void onModifierStarted(final IModifier<T> pModifier, final T pItem) {
        this.onModifierStarted(pItem);
    }

    @Override
    public void onModifierFinished(final IModifier<T> pModifier, final T pItem) {
        this.mFinished = true;
        this.mFinishedCached = true;
        this.onModifierFinished(pItem);
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
